﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Brio.Framework
{
    public class MotifPopulation : List<MotifChromosome>
    {
        public MotifPopulation()
        {
            generation = 0;
            run = 0;

            Statistics = new MotifStatistics();
        }

        private int generation, run;
        private double sumFitness, sumFitnessSquared;

        public int Generation
        {
            get { return generation; }
        }

        public int Run
        {
            get { return run; }
        }

        public MotifStatistics Statistics
        {
            get;
            set;
        }


        /// <summary>
        /// This function does statistics functions after computing the fitnesses of a population, namely:
        /// 1) Avg Fitness
        /// 2) Avg STD 
        /// </summary>
        public void ComputeGenerationalStatistics()
        {
            //TODO: Move math to MotifStatistics

            sumFitness = 0.0;
            sumFitnessSquared = 0.0;

            // Sum all of the chromosome's fitnesses.
            sumFitness = this.Sum(series => series.Fitness);
            // Sum all of the chromosome's fitnesses squared.
            sumFitnessSquared = this.Sum(series => Math.Pow(series.Fitness, 2.0));

            
            // Average Fitness of the generation
            double avgGenFitness = sumFitness / this.Count;

            // STD of average fitness of the generation
            double avgStdGenFitness = Math.Sqrt(
                                         Math.Abs(sumFitnessSquared -
                                                  sumFitness * sumFitness / this.Count)
                                                  /
                                                  (this.Count - 1));

            // Let our statistic class 
            Statistics.AddGeneration(avgGenFitness, avgStdGenFitness);


        }

        public void ComputeSingleRunStatistics()
        {
            Statistics.EndRun();
        }

        public void ComputeEntireRunStatistics()
        {
            Statistics.EndRuns();
        }

        public void ScaleFitnesses()
        {
            // Rank Scaled Fitness (Maximization)
            // TODO: Other Scaling Methods

            // Assumptions (For Group):
            // 1) When fitnesses are scaled they are already in a sorted list
            // 2) Rank fitnesses does not go off of actual fitness but namely assigns
            //    a number to each chromosome depending on their number (1 , 2, ... N)
            // 3) Therefore the sum of these scaled fitness is the sum from 1 ... N
            //    Remember Gauss? Yes. sum(1..N) = N(N+1) / 2
            // Goals:
            // Save a for loops

            // Gauss saves the day
            double sumScaledFitness = this.Count * (this.Count + 1) / 2.0;

            double sumProportionalizedScaledFitness = 0.0;


            // Set Proportionalized Scaled Fitness
            // NOTE : List is ordered ascending so the highest values are the end
            for (int i = this.Count - 1; i >= 0; i--)
            {
                this[i].ProportionalizedScaledFitness = i / sumScaledFitness;

                // do i need?
                sumProportionalizedScaledFitness += this[i].ProportionalizedScaledFitness;
            }

        }

        public void Select()
        {
            // Roulette Wheel Selection
            // TODO: Other Selection Methods

            MotifPopulation childPopulation = new MotifPopulation();

            double randomNumber, rouleteWheel;


            for (int i = 0; i < this.Count; i++)
            {
                randomNumber = Framework.Constants.Random.NextDouble();
                rouleteWheel = 0.0;

                for (int j = 0; j < this.Count; j++)
                {
                    rouleteWheel += this[j].ProportionalizedScaledFitness;

                    // This member should survive into the child population
                    if (randomNumber < rouleteWheel)
                    {
                        childPopulation.Add(this[j].Copy());
                        
                        break;
                    }
                    // This should never fire but just in case it does
                    // TODO : Why does this condition fire
                    else if (j == this.Count - 1)
                    {
                        childPopulation.Add(this[j].Copy());
                    }
                }

            }

            // Is this a good way of doing it?
            this.Clear();
            this.AddRange(childPopulation.GetRange(0, Settings.PopulationSize));

            // We are now entering the next generation
            generation++;
        }

        public void EndGeneration()
        {
            generation = 0;
            run++;
        }


        // Returns an array containing MotifChromosomes within the top percentage specified
        public List<MotifChromosome> GetTopPercentage(int percentage)
        {
            List<MotifChromosome> topChromosomes = new List<MotifChromosome>();
            int startingMember;
            double exactPercent;
            
            //calculate startingMember index
            exactPercent = ((100 - (double)percentage) / 100);
            startingMember = (int)(exactPercent * (double)Settings.PopulationSize);

            //add each chromosome from startingmember-1 to populationsize-1
            for (int i = startingMember; i <= Settings.PopulationSize; i++)
            {
                topChromosomes.Add(this[i - 1]);
            }

            return topChromosomes;
        }
    }
}
